/**
 * 临界区
 *
 * @author fireway
 * @since 2019年 01月 06日 星期日 06:45:37 CST
 */
package concurrency;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Not thread-safe
 *
 */
class Pair {
    private int mX;

    private int mY;

    public Pair(int x, int y) {
        mX = x;
        mY = y;
    }

    public Pair() {
        this(0, 0);
    }

    public int getX() {
        return mX;
    }

    public int getY() {
        return mY;
    }

    public void increaseX() {
        mX++;
    }

    public void increaseY() {
        mY++;
    }

    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append("x = ");
        sb.append(mX);
        sb.append(", y = ");
        sb.append(mY);
        return sb.toString();
    }

    public void checkState() {
        if (mX != mY) {
            throw new PairValuesNotEqualException();
        }
    }

    class PairValuesNotEqualException extends RuntimeException {
        public PairValuesNotEqualException() {
            super("Pair values not equal: " + Pair.this);
        }
    }
}

abstract class PairManager {
    protected Pair mPair = new Pair();

    private List<Pair> mList = Collections.synchronizedList(new ArrayList<Pair>());

    protected AtomicInteger mCounter = new AtomicInteger(0);

    public abstract void increase();

    public synchronized Pair getPair() {
        return new Pair(mPair.getX(), mPair.getY());
    }

    protected void store(Pair pair) {
        mList.add(pair);
        try {
            TimeUnit.MILLISECONDS.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class PairManager1 extends PairManager {
    @Override
    public synchronized void increase() {
        mPair.increaseX();
        mPair.increaseY();
        store(getPair());
    }
}

class PairManager2 extends PairManager {
    @Override
    public void increase() {
        Pair temp;

        synchronized(this) {
            mPair.increaseX();
            mPair.increaseY();
            temp = getPair();
        }

        store(temp);
    }
}

class PairManipulator implements Runnable {
    private PairManager mPm;

    public PairManipulator(PairManager pm) {
        System.out.println("PairManipulator.init() " + Thread.currentThread());
        mPm = pm;
    }

    @Override
    public void run() {
        System.out.println("PairManipulator.run() " + Thread.currentThread());
        while (true) {
            mPm.increase();
        }
    }

    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append("PairManipulator[pair:");
        sb.append(mPm.getPair());
        sb.append(", counter = ");
        sb.append(mPm.mCounter.get());
        sb.append("]");
        return sb.toString();
    }
}

class PairChecker implements Runnable {
    private PairManager mPm;

    public PairChecker(PairManager pm) {
        System.out.println("PairChecker.init() " + Thread.currentThread());
        mPm = pm;
    }

    @Override
    public void run() {
        System.out.println("PairChecker.run() " + Thread.currentThread());
        while (true) {
            mPm.mCounter.incrementAndGet();
            mPm.getPair().checkState();
        }
    }
}

public class CriticalSection {
    /**
     * Test the two different approaches
     */
    public static void testCompare(PairManager manager1, PairManager manager2) {
        ExecutorService exec = Executors.newCachedThreadPool();

        PairManipulator manipulator1 = new PairManipulator(manager1);
        PairManipulator manipulator2 = new PairManipulator(manager2);
        exec.execute(manipulator1);
        exec.execute(manipulator2);

        PairChecker checker1 = new PairChecker(manager1);
        PairChecker checker2 = new PairChecker(manager2);
        exec.execute(checker1);
        exec.execute(checker2);

        try {
            TimeUnit.MILLISECONDS.sleep(500);
        } catch(InterruptedException e) {
            System.out.println("Sleep interrupted");
        }
        System.out.println("manipulator1: " + manipulator1 + "\nmanipulator2: " + manipulator2);
        System.exit(0);
    }

    public static void main(String[] args) {
        PairManager manager1 = new PairManager1();
        PairManager manager2 = new PairManager2();
        testCompare(manager1, manager2);
    }
}
